package com.wonderful.compose.side_effect

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import com.wonderful.compose.ui.theme.ComposeLearnTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
 * Describe: 副作用
 *
 * @author Wonderful
 * @time 2025/9/15 15:56
 */
class SideEffectActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeLearnTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Counter()
                }
            }
        }
    }
}

@Composable
fun Counter(modifier: Modifier = Modifier) {
    val count = remember { mutableStateOf(0) }
    Column {
        Text(text = "${count.value}")
        Button(onClick = { count.value++ }) {
            Text(text = "Click me")
        }
    }
}

/******************* LaunchedEffect *********************/
/**
 * LaunchedEffect应该是最常用的一个Side-effects函数了，它主要用于解决两个问题。
 * 第一，让你在Composable函数中的某些代码只执行一遍。
 * 重组的其中一个特性，就是可能会导致Composable函数频繁重复执行，如果有些代码你并不想让它们在每次重组的时候都重新执行一遍，就可以使用LaunchedEffect函数来解决。
 * 第二，就是在Composable函数中提供一个协程作用域。
 */
@Composable
fun MyApp(modifier: Modifier = Modifier) {
    // LaunchedEffect函数中的逻辑会在每次参数发生变化时重新执行
    // 如果你想要的只是让你的某些代码只执行一遍，传入Unit就好了。
    LaunchedEffect(Unit) {
        // 对程序进行初始化
        delay(1000)
    }
}

/**
 * rememberUpdatedState
 * rememberUpdatedState几乎总是配合着LaunchedEffect函数一起使用的
 * 主要用于解决，在使用LaunchedEffect函数时，可能存在的一些回调丢失的风险。
 */
@Composable
fun Initialize(callback: () -> Unit) {
    // 在LaunchedEffect中的代码执行期间，callback参数随时是有可能改变的，
    // 如果LaunchedEffect函数还在初始化过程中，callback参数变动了，那么老的callback对象已经不存在了，
    // 自然无法回调成功，新的callback对象也无法得到回调，因为LaunchedEffect函数只会执行一次。
    // 使用rememberUpdatedState函数就可以解决这个问题
    val currentCallback by rememberUpdatedState(callback)
    LaunchedEffect(Unit) {
        delay(1000)
        //callback()
        currentCallback() // 这个currentCallback可以保证永远是指向的是最新的callback参数。
    }
    // 上面的代码是下面的缩写
    // val currentCallback = rememberUpdatedState(callback)
    // 调用：currentCallback.value()
}

/**
 * rememberCoroutineScope
 * rememberCoroutineScope这个Side-effects函数也是用于提供协程作用域的。
 * 它和 LaunchedEffect 是一个互补关系
 * LaunchedEffect 是一个Composable函数，只能在另一个Composable函数中调用
 * 如果要在一个非Composable函数中创建协程作用域的话，那么LaunchedEffect函数就无法做到了
 * 这个时候，就可以使用rememberCoroutineScope函数了
 * 如在 button 的点击事件回调中没有协程作用域
 */
@Composable
fun MyButton() {
    val scope = rememberCoroutineScope()
    Button(onClick = {
        scope.launch {
            delay(1000)
        }
    }) {
        Text(
            text = "This is Button",
            color = Color.White,
            fontSize = 26.sp
        )
    }
}

// 如果你觉得rememberCoroutineScope函数更加好用，想用来替代LaunchedEffect函数的话，那就错得离谱了。
// 比如下面这段代码：
// @Composable
// fun Initialize(callback: () -> Unit) {
//    val coroutineScope = rememberCoroutineScope()
//    coroutineScope.launch {
//        delay(1000)
//        callback()
//    }
// }
// 这段代码会产生严重的Side Effect，因为每次Initialize()函数发生重组都会导致开启一个新的协程并触发一次回调，
// 这绝对不是你想要的.

/**
 * DisposableEffect
 * DisposableEffect应该可以算是和LaunchedEffect对称的一个Side-effects函数，
 * 它的作用是对不再使用的资源进行安全合理地回收。
 * 语法如下:
 * DisposableEffect(param) {
 *     onDispose {
 *     }
 * }
 * DisposableEffect函数允许接收一个或多个参数，在参数不变的情况下，DisposableEffect函数中的内容只会执行一次。
 * 和LaunchedEffect不同点在于，DisposableEffect函数不会提供协程作用域，同时DisposableEffect函数中必须要再提供一个onDispose函数.
 * LaunchedEffect函数并不适用监听器的场景，因为它只负责添加，却不能删除，这样资源就无法回收了
 */
@Composable
fun MyLifecycleApp() {
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_CREATE -> {
                    println("ON_CREATE")
                }

                Lifecycle.Event.ON_START -> {
                    println("ON_START")
                }

                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

/**
 * produceState
 * produceState函数的作用，是将一个非Compose的State转换成Compose的State，因此这是一个关于State的Side-effects函数。
 * 它的语法如下:
 * produceState(initialValue, key1, key2, ...) {
 *     value = newValue
 * }
 * 其中initialValue是初始值，key1, key2, ... 是用于依赖收集的参数，当这些参数发生变化时，produceState函数会重新执行。
 * 而value = newValue 是将新值赋值给State，这个新值会被Compose框架自动收集到，从而触发Composable函数的重新composition。
 */
@Composable
fun HomePage(success: Boolean) {
    var loadingStatus by remember { mutableStateOf("") }
    // 模拟 加载中、加载成功和加载失败这三种页面状态
    LaunchedEffect(Unit) {
        loadingStatus = "Status.LOADING"
        delay(1000)
        loadingStatus = if (success) {
            "Status.SUCCESS"
        } else {
            "Status.ERROR"
        }
    }
    when (loadingStatus) {
        "Status.LOADING" -> {
            //LoadingContent()
        }

        "Status.SUCCESS" -> {
            //HomePageContent()
        }

        "Status.ERROR" -> {
            //ErrorContent()
        }
    }
}

// 使用 produceState 优化如下
// 相比之下，使用produceState函数的版本要更加清爽一些。
// 不用像刚才那样还需要在调用mutableIntStateOf函数时传入一个无意义的状态""用作于初始化。
// produceState函数允许通过initialValue参数来设置初始值，这样每个状态都是有意义的。
// 同时，使用produceState函数得到的转换后的State对象是可以声明成val的，这样可以避免一些加载状态被误改的情况，从而让代码变得更加安全
@Composable
fun HomePageWithProduceState(success: Boolean) {
    val loadingStatus by produceState(initialValue = "Status.LOADING") {
        delay(1000)
        value = if (success) {
            "Status.SUCCESS"
        } else {
            "Status.ERROR"
        }
    }
    when (loadingStatus) {
        "Status.LOADING" -> {
            //LoadingContent()
        }

        "Status.SUCCESS" -> {
            //HomePageContent()
        }

        "Status.ERROR" -> {
            //ErrorContent()
        }
    }
}

// 另外，还可以将produceState函数里的这段逻辑单独抽离成一个函数并放在任何你想放的位置，
// 这样可以更好地将业务代码和UI代码分离开，如下所示：
@Composable
fun startLoading(success: Boolean) = produceState(initialValue = "Status.LOADING") {
    delay(1000)
    value = if (success) {
        "Status.SUCCESS"
    } else {
        "Status.ERROR"
    }
}

@Composable
fun HomePageWithProduceState2(success: Boolean) {
    val loadingStatus by startLoading(success)
    when (loadingStatus) {
        "Status.LOADING" -> {
            //LoadingContent()
        }

        "Status.SUCCESS" -> {
            //HomePageContent()
        }

        "Status.ERROR" -> {
            //ErrorContent()
        }
    }
}

/**
 * SideEffect
 * 这个函数应该是所有Side-effects函数里面最不常用的一个
 * 作用是可以让其函数中的代码在Composable函数每次重组的时候执行一次。
 * 比如可以用在计算Composable函数发生了多少次重组。
 * 甚至觉得SideEffect函数最大的作用是用于当做学习理解重组概念的调试函数
 */
@Composable
fun MyButton2() {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = {
        count++
    }) {
        Text(
            text = "Count is $count!",
        )
        SideEffect {
            println("Recomposition happened.")
        }
    }
}
// 官方例子
// 实现的效果是，每次函数重组，都更新一下用户类型这个字段，以确保Firebase上统计到的用户类型数据是最新的。
//@Composable
//fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
//    val analytics: FirebaseAnalytics = remember {
//        FirebaseAnalytics()
//    }
//
//    // On every successful composition, update FirebaseAnalytics with
//    // the userType from the current User, ensuring that future analytics
//    // events have this metadata attached
//    SideEffect {
//        analytics.setUserProperty("userType", user.userType)
//    }
//    return analytics
//}

/**
 * derivedStateOf
 * derivedStateOf 这个函数非常有用，可以大幅改善Compose代码的运行效率。
 * 属于Side-effects函数中的一种。
 */